from __future__ import absolute_import, print_function, unicode_literals, division
from future_builtins import * #@UnusedWildImport
if __name__ == '__main__':
    __package__ = b'pyson.schema' #@ReservedAssignment
    import pyson.schema #@UnusedImport

from .schema_class import Schema
from .sch_containers import Array, Object
from .util import get_schemas

class Type(Schema):
    '''
    Schema that tests if object is of given type.
    '''

    def __init__(self, tt, *args, **kwds):
        self.exact = kwds.pop('exact', False)
        self.type = tt
        super(Type, self).__init__(*args, **kwds)

    def isvalid(self, obj, **kwds):
        if self.exact:
            return type(obj) is self.type
        else:
            return isinstance(obj, self.type)

def type_schema_factory(tt, name=None):
    '''
    Factory function that creates a new Schema validator that tests if objects
    are of the given type 'tt'.
    
    Examples
    --------
    
    >>> MyFloat = type_schema_factory(float)
    >>> MyFloat().isvalid(1)
    False
    >>> MyFloat().isvalid(1.0)
    True
    '''
    if name is None:
        name = tt.__name__.title()

    class TypeCheck(Type):
        def __init__(self, *args, **kwds):
            super(TypeCheck, self).__init__(tt, *args, **kwds)
    TypeCheck.__name__ = name

    return TypeCheck

# These classes are derived from Schema in order to suggest to code analysis 
# tools can infer that instances of these classes will be Schema instances
# Of course, the base class is completely ignored in the metaclass mechanism 
# and could be any class at all. 
class sch_instance(Schema):
    class __metaclass__(type):
        '''
        Optional fields should start with double underscores 
        
        Examples
        --------
        
        Schema initialized using the class inheritance notation
        
        >>> class Date(sch_instance):
        ...     year = Int()
        ...     month = Int()
        ...     day = Int()
        ...     is_end_of_the_world = Opt(Bool())
        >>> Date().isvalid({'year': 2012, 'month': 12, 'day': 12})
        True
        
        '''
        def __new__(cls, name, bases, dic):
            # Allows the creation of the SchemaObj parent type 
            if name == 'sch_instance':
                return type.__new__(cls, name, bases, dic)

            # No subtypes of SchemaObj will be created, instead they will 
            # instaciate a Object() schema
            del dic['__module__']

            descr = dic.get('__doc__', '')
            return Object(dic, label=name, descr=descr)

class array_of(Schema):
    class __metaclass__(type):
        '''
        Examples
        --------
        
        Schema initialized using the class inheritance notation
        
        >>> class DateLst(array_of):
        ...     year = Int()
        ...     month = Int()
        ...     day = Int()
        >>> DateLst().isvalid([{'year': 2012, 'month': 12, 'day': 12}])
        True
        
        '''
        def __new__(cls, name, bases, dic):
            # Allows the creation of the SchemaObj parent type 
            if name == 'array_of':
                return type.__new__(cls, name, bases, dic)

            # No subtypes of SchemaObj will be created, instead they will 
            # instaceate a Object() schema
            tt = sch_instance.__metaclass__(name + 'Item', bases, dic)
            return Array(tt)

__all__ = get_schemas(globals()) + ['sch_instance', 'array_of', 'type_schema_factory']
if __name__ == '__main__':
    from . import *
    import doctest
    doctest.testmod(optionflags=doctest.REPORT_ONLY_FIRST_FAILURE, verbose=0)
